<?php
/**
 * @package qBase
 * @subpackage base
 */

/**
 * Automate the load of classes
 * Lowercase Files WILL BE SKIPPED !
 * The Autoload pattern is : ClassName.php or Classname.php
 */
function __autoload($class_name)
{
	// automate load of classes
	QAutoload::AutoloadClass($class_name);
}

/**
 * Helper class for the autoload of classes
 * @package qBase
 * @subpackage base
 */
final class QAutoload extends QObject 
{
	/**
	 * The folders to consider for Autoload
	 *
	 * @var array
	 */
	private static $WatchFolders = array();
	/**
	 * The list of classes in the framework (class_name => path)
	 *
	 * @var array
	 */
	private static $Classes = array();

	/**
	 * The list of classes in the framework with link to their watch folder (class_name => WatchFolder)
	 *
	 * @var array
	 */
	private static $ClassesWatchFolders = array();

	/**
	 * Automate the load of classes
	 * Will always skip the "temp" and "res" folders in the root of the watch folder
	 *
	 * @param string $class_name the name of the class to be loaded
	 */
	public static function AutoloadClass($class_name)
	{
		if (isset(self::$Classes[$class_name]))
		{
			if (file_exists(self::$Classes[$class_name]))
			{
				require_once(self::$Classes[$class_name]);
				return;
			}
			else 
				unset(self::$Classes[$class_name]);
		}
		
		self::RefreshObjectsTree();
		if (isset(self::$Classes[$class_name]))
			require_once(self::$Classes[$class_name]);
		else 
			throw new Exception("Class [{$class_name}] was not found");
	}
	
	/**
	 * Sets the path of a class
	 *
	 * @param string $class_name
	 * @param string $path
	 */
	public static function SetClass($class_name, $path, $watch_folder = null)
	{
		self::$Classes[$class_name] = $path;
		if ($watch_folder)
			self::$ClassesWatchFolders[$class_name] = $watch_folder;
	}
	
	/**
	 * Loads all classes in the watch folders
	 *
	 */
	public static function LoadAllClasses()
	{
		foreach (self::$Classes as $class => $path)
			self::AutoloadClass($class);
	}
	
	/**
	 * Gets the list of classes that can be autoloaded (class_name => path)
	 *
	 * @return array
	 */
	public static function GetClasses()
	{
		return self::$Classes;
	}
	
	/**
	 * Loads the list of classes and their path from "/res/misc/objectsTree.txt"
	 * @param string $only_folder will only load one Watch folder
	 * @param boolean $append will append to the list instead of rebuilding it
	 *
	 */
	public static function LoadClassesInfo($only_folder = null, $append = true)
	{
		$use_list = null;
		if ($only_folder)
			$use_list = array($only_folder);
		else 
		{
			if (!in_array(__QBasePath, self::$WatchFolders))
				self::$WatchFolders[] = __QBasePath;
			$use_list = self::$WatchFolders;
		}

		if (!$append)
			self::$Classes = array();
		foreach ($use_list as $Path)
		{
			$autoload_file = $Path . "temp/phpcache/autoload.php";
			if (!file_exists($autoload_file))
			{
				$dir = $Path . "temp/phpcache/";
				if (!is_dir($dir))
					mkdir($dir, 0755, true);
				file_put_contents($autoload_file, "<?php\n\n?>");
			}
			include($Path . "temp/phpcache/autoload.php");
		}
	}
	
	/**
	 * Refreshes the list of classes
	 * Will always skip the "temp" and "res" folders in the root of the watch folder
	 * 
	 * @param string $only_folder will only refresh one Watch folder
	 * @param boolean $append will append to the list instead of rebuilding it
	 *
	 */
	public static function RefreshObjectsTree($only_folder = null, $append = false)
	{
		$use_list = null;
		if ($only_folder)
			$use_list = array($only_folder);
		else 
		{
			if (!in_array(__QBasePath, self::$WatchFolders))
				self::$WatchFolders[] = __QBasePath;
			$use_list = self::$WatchFolders;
		}
		
		if ($append)
		{
			$full_list = self::$Classes;
			$full_list_watch = self::$ClassesWatchFolders;
		}
		else
		{
			$full_list = array();
			$full_list_watch = array();
		}
		foreach ($use_list as $Path)
		{
			$list = array();
			$list_watch = array();
			self::RefreshObjectsTreeRecurse($Path, $list, $list_watch, $Path);
	
			// public static function SetClass($class_name, $path)
			$text = "<?php\n";
			foreach ($list as $k => $v)
			{
				$text .= "QAutoload::SetClass(\"".addslashes($k)."\", \"".addslashes($v)."\", \"".addslashes($Path)."\");\n";
			}
			
			$text .= "?>";

			file_put_contents($Path . "temp/phpcache/autoload.php", $text);
			$full_list = array_merge($full_list, $list);
			$full_list_watch = array_merge($full_list_watch, $list_watch);
		}

		self::$Classes = $full_list;
		self::$ClassesWatchFolders = $full_list_watch;
	}
	
	/**
	 * Recursive function for RefreshObjectsTree
	 *
	 * @param string $path
	 * @param array $list
	 * @param array $list_watch the list of watch folder per class
	 * @param string $watch_folder the current watch folder
	 * 
	 */
	private static function RefreshObjectsTreeRecurse($path, &$list, &$list_watch, $watch_folder)
	{
		$items = scandir($path);
		
		foreach ($items as $itm)
		{
			if (strlen(trim($itm, ". \t\r\n")) == 0)
				continue;
				
			if (($path == $watch_folder) && (($itm == "res") || ($itm == "temp")))
				continue;
			
			$p = rtrim($path, "/") . "/" . ltrim($itm, "/");
			
			if (is_file($p))
			{
				$dot_pos = strrpos($itm, ".");
				$ext = ($dot_pos === false) ? null : substr($itm, $dot_pos + 1);
				if ($ext == "php")
				{
					$class_name = substr($itm, 0, $dot_pos);
					// skip lowercase files
					if ($class_name != strtolower($class_name))
					{
						$list[$class_name] = $p;
						$list_watch[$class_name] = $watch_folder;
					}
				}
			}
			else 
			{
				self::RefreshObjectsTreeRecurse($p, $list, $list_watch, $watch_folder);
			}
		}
	}
	
	/**
	 * Ads a folder that contains classes to be autoloaded
	 * Will always skip the "temp" and "res" folders in the root of the watch folder
	 *
	 * @param string $folder
	 * @param boolean $refresh_tree if true will refresh the tree from the filesystem
	 */
	public static function AddWatchFolder($folder, $set_as_runtime_folder = false, $refresh_tree = false)
	{
		if (!in_array($folder, self::$WatchFolders))
			self::$WatchFolders[] = $folder;
			
		if ($refresh_tree)
			self::RefreshObjectsTree($folder);
		else 
			self::LoadClassesInfo($folder, true);
			
		if ($set_as_runtime_folder)
		{
			QBaseInit::SetRuntimeFile(rtrim($folder, "\\/") . "/temp/phpcache/runtime.php");
		}
	}
	
	/**
	 * Not implemented yet
	 *
	 * @param string $folder
	 */
	public static function RemoveWatchFolder($folder)
	{
		throw new Exception("RemoveWatchFolder is not implemented yet");
	}
	
	/**
	 * Gets the full path to the file where the object is defined 
	 *
	 * @param mixed $class The $class parameter may be an instance of an object or a string with the name of the object
	 * @return string
	 */
	public static function GetClassPath($class)
	{
		$class_name = null;
		if (is_object($class))
			$class_name = get_class($class);
		else if (is_string($class))
			$class_name = $class;
		else 
			throw new Exception("QAutoload::GetClassPath the \$class parameter must be a string or the instance of an object");
		
		if (isset(self::$Classes[$class_name]))
			return self::$Classes[$class_name];
		else 
			return null;
	}
	
	/**
	 * Gets the name of the folder where the class is stored 
	 * The dir name is not buffered only the full path is 
	 * the `dirname` functino will be called on each call to this function
	 *
	 * @param string $class
	 * @return string
	 */
	public static function GetClassDir($class)
	{
		$path = self::GetClassPath($class);
		if ($path)
			return dirname($path) . "/";
		else 
			return null;
	}
	
	/**
	 * Gets the watch path for the specified class
	 *
	 * @param string $class
	 * @return string
	 */
	public static function GetClassWatchPath($class)
	{
		$class_name = null;
		if (is_object($class))
			$class_name = get_class($class);
		else if (is_string($class))
			$class_name = $class;
		else 
			throw new Exception("QAutoload::GetClassPath the \$class parameter must be a string or the instance of an object");
		
		if (isset(self::$ClassesWatchFolders[$class_name]))
			return self::$ClassesWatchFolders[$class_name];
		else 
			return null;
	}
	/**
	 * Gets the framework relative path to the file where the object is defined 
	 *
	 * @param mixed $class The $class parameter may be an instance of an object or a string with the name of the object
	 * @return string
	 */
	public static function GetRelativeClassPath($class)
	{
		$class_name = null;
		if (is_object($class))
			$class_name = get_class($class);
		else if (is_string($class))
			$class_name = $class;
		else 
			throw new Exception("QAutoload::GetClassPath the \$class parameter must be a string or the instance of an object");
		
		if (isset(self::$Classes[$class_name]))
		{
			$path = self::$Classes[$class_name];
			return substr($path, strlen(self::$ClassesWatchFolders[$class_name]));
		}
		else 
			return null;
	}
	
	/**
	 * Prints the objects that can be autoloaded and their definition file
	 *
	 */
	public static function PrintLoadableClasses()
	{
		echo "<h3>Classes that can be autoloaded: </h3>";
		
		$classes = self::GetClasses();
		foreach ($classes as $k => $v)
		{
			echo "$k: ".QAutoload::GetClassPath($k)."<br/>";
		}
	}
}



?>